package fr.afcepf.atod16.groupe1.projet2.dao;

import fr.afcepf.atod16.groupe1.projet2.dto.Dto;
import fr.afcepf.atod16.groupe1.projet2.entities.Entite;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.LockTimeoutException;
import javax.persistence.PersistenceException;
import javax.persistence.PessimisticLockException;
import javax.persistence.QueryTimeoutException;
import javax.persistence.TransactionRequiredException;
import javax.persistence.criteria.CriteriaQuery;

/**
 * DAO abstraite implementant les méthodes obligatoires des DAOs de manière
 * générique.<br/>
 * Attention : le type d'Entité et de DTO doivent concorder !
 * @see Dao
 * @author cedric
 * @param <T> Le type d'Entite sur laquelle s'appliquera la DAO
 * @param <U> Le type de DTO sur lequel s'appliquera la DAO
 */
public abstract class DaoAbstract<T extends Entite, U extends Dto> {

    private Class<T> entityClass;

    /**
     * Constructeur acceptant en paramètre la référence de l'objet Class 
     * correspondant à l'entité : ce paramètre est fourni par les sous-classes
     * car le contexte de persistance doit être initialisé sur les DAO concrètes.
     * @param entityClass La référence de l'objet Class
     */
    public DaoAbstract(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    /**
     * Retourne la référence de l'entityManager initialiser sur les DAO concrètes.
     * @return La référence de l'entityManager
     */
    protected abstract EntityManager getEntityManager();

    /**
     * Permet d'ajouter un objet en base
     * @param dto Le DTO support des données à inserer
     * @return La valeur de la clé primaire autogénéré
     */
    public Integer ajouter(U dto) {
        Integer id = null;
        try {
            T entity = (T) DaoAssembleur.createEntity(dto);
            getEntityManager().persist(entity);
            getEntityManager().flush();
            id = entity.getId();
        } catch (EntityExistsException e) {
            System.out.println("l'entité existe déja");
        } catch (TransactionRequiredException e) {
            System.out.println("ou est la transaction ?");
        } catch (PersistenceException e) {
            System.out.println("le flush a echoué");
        } catch (IllegalArgumentException e) {
            System.out.println("donnez moi une entité !");
        }
        return id;
    }

    /**
     * Permet de modifier un objet en base
     * @param dto Le DTO contenant les données modifiées et la clé primaire 
     * indentifiant l'objet à modifier
     * @return True si la modification a réussi, False sinon
     */
    public Boolean modifier(U dto) {
        T entite;
        try {
            entite = getEntityManager().find(entityClass, dto.getId());
            if (entite != null) {
                DaoAssembleur.updateEntity(entite, dto);
            } else {
                System.out.println("n'existe pas !");
                return false;
            }
        } catch (IllegalArgumentException e) {
            System.out.println("argument illégal !");
            return true;
        }
        return true;
    }

    /**
     * Permet de supprimer un objet en base
     * @param dto Le DTO contenant la clé primaire de l'objet à supprimer
     * @return True si la suppression a réussi, False sinon
     */
    public Boolean supprimer(U dto) {
        try {
            T entity = getEntityManager().find(entityClass, dto.getId());
            if (entity != null) {
                getEntityManager().remove(entity);
            } else {
                System.out.println("rien à supprimer !");
                return false;
            }

        } catch (IllegalArgumentException e) {
            System.out.println("argument illégal !");
            return false;
        } catch (TransactionRequiredException e) {
            System.out.println("ou est la transaction ?");
            return false;
        }
        return true;
    }

    /**
     * Permet de retourner un objet depuis la base
     * @param id La clé primaire dl'objet à retourner
     * @return L'objet correspondant, null si aucun objet trouvé
     */
    public U rechercherParId(Integer id) {
        T entite;
        U dto = null;
        try {
            entite = getEntityManager().find(entityClass, id);
            if (entite != null) {
                dto = (U) DaoAssembleur.createDto(entite);
            } else {
                System.out.println("n'existe pas !");
            }
        } catch (IllegalArgumentException e) {
            System.out.println("argument illégal !");
        }
        return dto;
    }

    /**
     * Permet de retourner la liste des objets enregistré en base
     * @return La liste
     */
    public List<U> lister() {
        List<T> entites;
        U dto;
        List<U> dtos = new ArrayList<U>();
        
        try {
            CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
            cq.select(cq.from(entityClass));
            entites = getEntityManager().createQuery(cq).getResultList();

            for (T entite : entites) {
                dto = (U) DaoAssembleur.createDto(entite);
                dtos.add(dto);
            }
        }
        catch(IllegalStateException e) {
            System.out.println("entity manager fermé OU requete non autorisé");
        }
        catch(IllegalArgumentException e) {
            System.out.println("probleme de criteria");
        }
        catch(QueryTimeoutException e) {
            System.out.println("query timeout");
        }
        catch(TransactionRequiredException e) {
            System.out.println("transaction required !");
        }
        catch(PessimisticLockException e) {
            System.out.println("echec de lock");
        }
        catch(LockTimeoutException e) {
            System.out.println("echec de lock");
        }
        catch(PersistenceException e) {
            System.out.println("query timeout");
        }

        return dtos;
    }
}
